all repos — caroster @ 8cf97f406bef2a68157869e2f6398d45b8bec77a

[Octree] Group carpool to your event https://caroster.io

frontend/pages/e/[uuid]/details.tsx (view raw)

  1import moment from 'moment';
  2import Tooltip from '@mui/material/Tooltip';
  3import IconButton from '@mui/material/IconButton';
  4import Button from '@mui/material/Button';
  5import Box from '@mui/material/Box';
  6import Link from '@mui/material/Link';
  7import Card from '@mui/material/Card';
  8import Container from '@mui/material/Container';
  9import TextField from '@mui/material/TextField';
 10import Typography from '@mui/material/Typography';
 11import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
 12import EventIcon from '@mui/icons-material/Event';
 13import TuneIcon from '@mui/icons-material/Tune';
 14import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
 15import {useTheme} from '@mui/material/styles';
 16import {DatePicker} from '@mui/x-date-pickers/DatePicker';
 17import {PropsWithChildren, useState} from 'react';
 18import {useTranslation} from 'react-i18next';
 19import pageUtils from '../../../lib/pageUtils';
 20import ShareEvent from '../../../containers/ShareEvent';
 21import PlaceInput from '../../../containers/PlaceInput';
 22import usePermissions from '../../../hooks/usePermissions';
 23import useEventStore from '../../../stores/useEventStore';
 24import useToastStore from '../../../stores/useToastStore';
 25import EventLayout, {TabComponent} from '../../../layouts/Event';
 26import {
 27  EventByUuidDocument,
 28  useUpdateEventMutation,
 29} from '../../../generated/graphql';
 30
 31interface Props {
 32  eventUUID: string;
 33  announcement?: string;
 34}
 35
 36const Page = (props: PropsWithChildren<Props>) => {
 37  return <EventLayout {...props} Tab={DetailsTab} />;
 38};
 39
 40const DetailsTab: TabComponent = ({}) => {
 41  const {t} = useTranslation();
 42  const {
 43    userPermissions: {canEditEventDetails},
 44  } = usePermissions();
 45  const theme = useTheme();
 46  const [updateEvent] = useUpdateEventMutation();
 47  const addToast = useToastStore(s => s.addToast);
 48  const setEventUpdate = useEventStore(s => s.setEventUpdate);
 49  const event = useEventStore(s => s.event);
 50  const [isEditing, setIsEditing] = useState(false);
 51
 52  if (!event) return null;
 53
 54  const onSave = async e => {
 55    try {
 56      const {uuid, ...data} = event;
 57      const {id, travels, waitingPassengers, __typename, ...input} = data;
 58      await updateEvent({
 59        variables: {
 60          uuid,
 61          eventUpdate: {
 62            ...input,
 63          },
 64        },
 65        refetchQueries: ['eventByUUID'],
 66      });
 67      setIsEditing(false);
 68    } catch (error) {
 69      console.error(error);
 70      addToast(t('event.errors.cant_update'));
 71    }
 72  };
 73
 74  const modifyButton = isEditing ? (
 75    <Tooltip
 76      title={t('event.details.save')}
 77      sx={{
 78        position: 'absolute',
 79        top: theme.spacing(2),
 80        right: theme.spacing(2),
 81      }}
 82    >
 83      <IconButton color="primary" onClick={onSave}>
 84        <CheckCircleOutlineIcon />
 85      </IconButton>
 86    </Tooltip>
 87  ) : (
 88    <Tooltip
 89      title={t('event.details.modify')}
 90      sx={{
 91        position: 'absolute',
 92        top: theme.spacing(2),
 93        right: theme.spacing(2),
 94      }}
 95    >
 96      <IconButton color="primary" onClick={() => setIsEditing(true)}>
 97        <TuneIcon />
 98      </IconButton>
 99    </Tooltip>
100  );
101
102  return (
103    <Box
104      sx={{
105        position: 'relative',
106      }}
107    >
108      <Container
109        sx={{
110          p: 4,
111          mt: 6,
112          mb: 11,
113          mx: 0,
114          [theme.breakpoints.down('md')]: {
115            p: 2,
116          },
117        }}
118      >
119        <Card
120          sx={{
121            position: 'relative',
122            maxWidth: '100%',
123            width: '350px',
124            p: 2,
125          }}
126        >
127          <Typography variant="h4" pb={2}>
128            {t('event.details')}
129          </Typography>
130          {canEditEventDetails && modifyButton}
131          <Box pt={2} pr={1.5}>
132            <Typography variant="overline">{t('event.fields.name')}</Typography>
133            <Typography variant="body1">
134              {isEditing ? (
135                <TextField
136                  size="small"
137                  fullWidth
138                  value={event.name}
139                  onChange={e => setEventUpdate({name: e.target.value})}
140                  name="name"
141                  id="EditEventName"
142                />
143              ) : (
144                <Typography variant="body1" id="EventName">
145                  {event.name ?? t('event.fields.empty')}
146                </Typography>
147              )}
148            </Typography>
149          </Box>
150          <Box pt={2} pr={1.5}>
151            <Typography variant="overline">{t('event.fields.date')}</Typography>
152            {isEditing ? (
153              <Typography variant="body1">
154                <DatePicker
155                  slotProps={{
156                    textField: {
157                      size: 'small',
158                      id: `EditEventDate`,
159                      fullWidth: true,
160                      placeholder: t('event.fields.date_placeholder'),
161                    },
162                  }}
163                  format="DD/MM/YYYY"
164                  value={moment(event.date)}
165                  onChange={date =>
166                    setEventUpdate({
167                      date: !date ? null : moment(date).format('YYYY-MM-DD'),
168                    })
169                  }
170                />
171              </Typography>
172            ) : (
173              <Box position="relative">
174                <Typography variant="body1" id="EventDate">
175                  {event.date
176                    ? moment(event.date).format('DD/MM/YYYY')
177                    : t('event.fields.empty')}
178                </Typography>
179                <EventIcon
180                  color="action"
181                  sx={{
182                    position: 'absolute',
183                    right: theme.spacing(-0.5),
184                    top: 0,
185                  }}
186                />
187              </Box>
188            )}
189          </Box>
190          <Box pt={2} pr={1.5}>
191            <Typography variant="overline">
192              {t('event.fields.address')}
193            </Typography>
194            {isEditing ? (
195              <PlaceInput
196                place={event.address}
197                latitude={event.latitude}
198                longitude={event.longitude}
199                onSelect={({place, latitude, longitude}) =>
200                  setEventUpdate({
201                    address: place,
202                    latitude,
203                    longitude,
204                  })
205                }
206              />
207            ) : (
208              <Box position="relative">
209                <Typography variant="body1" id="EventAddress" sx={{pr: 3}}>
210                  {event.address ? (
211                    <Link
212                      target="_blank"
213                      rel="noreferrer"
214                      href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
215                        event.address
216                      )}`}
217                      onClick={e => e.preventDefault}
218                    >
219                      {event.address}
220                    </Link>
221                  ) : (
222                    t('event.fields.empty')
223                  )}
224                </Typography>
225                <PlaceOutlinedIcon
226                  color="action"
227                  sx={{
228                    position: 'absolute',
229                    right: theme.spacing(-0.5),
230                    top: 0,
231                  }}
232                />
233              </Box>
234            )}
235          </Box>
236          <Box pt={2} pr={1.5}>
237            <Typography variant="overline">
238              {t('event.fields.description')}
239            </Typography>
240            {isEditing ? (
241              <Typography variant="body1">
242                <TextField
243                  fullWidth
244                  multiline
245                  maxRows={4}
246                  inputProps={{maxLength: 250}}
247                  value={event.description || ''}
248                  onChange={e => setEventUpdate({description: e.target.value})}
249                  id={`EditEventDescription`}
250                  name="description"
251                />
252              </Typography>
253            ) : (
254              <Typography variant="body1" id="EventDescription" sx={{pr: 3}}>
255                {event.description ?? t('event.fields.empty')}
256              </Typography>
257            )}
258          </Box>
259          {!isEditing && (
260            <ShareEvent
261              title={`Caroster ${event.name}`}
262              sx={{width: '100%', mt: 2}}
263            />
264          )}
265        </Card>
266      </Container>
267    </Box>
268  );
269};
270
271export const getServerSideProps = pageUtils.getServerSideProps(
272  async (context, apolloClient) => {
273    const {uuid} = context.query;
274    const {host = ''} = context.req.headers;
275    let event = null;
276
277    // Fetch event
278    try {
279      const {data} = await apolloClient.query({
280        query: EventByUuidDocument,
281        variables: {uuid},
282      });
283      event = data?.eventByUUID?.data;
284    } catch (error) {
285      return {
286        notFound: true,
287      };
288    }
289
290    return {
291      props: {
292        eventUUID: uuid,
293        metas: {
294          title: event?.attributes?.name || '',
295          url: `https://${host}${context.resolvedUrl}`,
296        },
297      },
298    };
299  }
300);
301export default Page;